﻿Imports System.IO
Imports System.Threading

''' <summary>
''' Provides a method to securely delete a given file using the Gutmann algorithm (35 passes)
''' </summary>
''' <remarks>"Gutmann Algorithm Secure File Deletion" project belongs to "xorcrash" on SourceForge http://sourceforge.net/projects/gutmannmethod/.
''' It was converted from C# to VB and adapted to this application</remarks>
Public Class GutmannAlgorithm
    Implements IDisposable

#Region "Class declarations"
    ' Class constructor argument
    Private filePath As String
    ' Set the block size we going to work with. Setting it higher sometimes impacts negatively on the performance.
    Private Const FILE_BLOCK As Integer = 8192
    ' Create a random number generator
    Private randomNumber As New Random()
    ' Create a long that will hold the file lenght
    Private fileLength As Long
    ' Create an integer that will hold the position last block of bytes
    Private lastBlock As Integer
    ' Create an integer that will hold the last block of bytes
    Private lastBlockBytes As Byte()
    ' Create a long that will hold the reminder of fileLenght divided by FILE_BLOCK
    Private modWrite As Long
    ' Initialize the first byte array that will be written
    Private bytes As Byte() = New Byte(FILE_BLOCK - 1) {}
#End Region

    ' The class constructor
    Public Sub New(path As String)
        MyClass.filePath = path
    End Sub

    ''' <summary>
    ''' Run the Gutmann algorithm
    ''' </summary>
    ''' <remarks></remarks>
    Public Async Function GutmannSecureDelete(Optional token As CancellationToken = Nothing, Optional pauseToken As PauseToken = Nothing) As Task
        ' Get detailed information about selected file.
        Dim fileInf As New FileInfo(filePath)

        ' Initialize a byte
        Dim byteSelect As Byte = &H0

        ' Start the main loop asyncronously. On each pass, Switch determines what kind of action shold be taken. 
        ' Procedure follows Gutmann algorithm and writes specific for each step values to the file.
        Await Task.Run(Async Function()
                           For i As Integer = 0 To 34

                               If Not token = Nothing Then
                                   ' Throw the cancellation request if token has one
                                   token.ThrowIfCancellationRequested()
                               End If
                               If Not pauseToken.Equals(Nothing) Then
                                   Await pauseToken.WaitWhilePausedAsync()
                               End If

                               Select Case i
                                   Case 0, 1, 2, 3
                                       randomBytePass(fileInf)
                                       Exit Select
                                   Case 4
                                       bytePass(fileInf, &H55)
                                       Exit Select
                                   Case 5
                                       bytePass(fileInf, &HAA)
                                       Exit Select
                                   Case 6
                                       gutmannBytePass(fileInf, &H92, &H49, &H24)
                                       Exit Select
                                   Case 7, 25
                                       gutmannBytePass(fileInf, &H49, &H24, &H92)
                                       Exit Select
                                   Case 8, 26
                                       gutmannBytePass(fileInf, &H24, &H92, &H49)
                                       Exit Select
                                   Case 9, 27
                                       bytePass(fileInf, byteSelect)
                                       Exit Select
                                   Case 10, 11, 12, 13, 14, 15, _
                                       16, 17, 18, 19, 20, 21, _
                                       22, 23
                                       byteSelect += &H11
                                       bytePass(fileInf, byteSelect)
                                       Exit Select
                                   Case 24
                                       byteSelect += &H11
                                       bytePass(fileInf, byteSelect)
                                       byteSelect = &H0
                                       Exit Select
                                   Case 28
                                       gutmannBytePass(fileInf, &H6D, &HB6, &HDB)
                                       Exit Select
                                   Case 29
                                       gutmannBytePass(fileInf, &HB6, &HDB, &H6D)
                                       Exit Select
                                   Case 30
                                       gutmannBytePass(fileInf, &HDB, &H6D, &HB6)
                                       Exit Select
                                   Case 31, 32, 33, 34
                                       randomBytePass(fileInf)
                                       Exit Select
                               End Select
                           Next

                           ' Delete the file after finishing Gutmann 35 passes.
                           ' NOTE: File delete is handled in the "ClearFolderContent sub 
                           ' that belongs to the IOUtilities class
                           ''''     fileInf.Delete()
                       End Function)
    End Function

    ''' <summary>
    ''' This procedure determines how many blocks of data will be in the file and also checks the size of the last block.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub randomBytePass(fileInf As FileInfo)
        MyClass.fileLength = fileInf.Length
        MyClass.lastBlock = CInt(fileLength Mod FILE_BLOCK)
        MyClass.lastBlockBytes = New Byte(lastBlock - 1) {}
        MyClass.modWrite = CInt(fileLength / FILE_BLOCK)

        For i As Integer = 0 To FILE_BLOCK - 1
            bytes(i) = Convert.ToByte(randomNumber.[Next](255))
        Next

        For i As Integer = 0 To lastBlock - 1
            lastBlockBytes(i) = Convert.ToByte(randomNumber.[Next](255))
        Next

        ' Prepared byte data is then written to the file overwriting existing content.
        Using st As Stream = File.OpenWrite(fileInf.FullName)
            For i As Long = 0 To modWrite - 1
                st.Write(bytes, 0, FILE_BLOCK)
                st.Flush()
            Next

            st.Write(lastBlockBytes, 0, lastBlock)
        End Using
    End Sub

    ''' <summary>
    ''' Almost the same procedure as randomBytePass, only difference is that byte is not random
    ''' but passed in as a parameter.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub bytePass(fileInf As FileInfo, useByte As Byte)
        MyClass.fileLength = fileInf.Length
        MyClass.lastBlock = CInt(fileLength Mod FILE_BLOCK)
        MyClass.lastBlockBytes = New Byte(lastBlock - 1) {}
        MyClass.modWrite = CInt(fileLength / FILE_BLOCK)

        For i As Integer = 0 To FILE_BLOCK - 1
            bytes(i) = useByte
        Next

        For i As Integer = 0 To lastBlock - 1
            lastBlockBytes(i) = useByte
        Next

        Using st As Stream = File.OpenWrite(fileInf.FullName)
            For i As Long = 0 To modWrite - 1
                st.Write(bytes, 0, FILE_BLOCK)
                st.Flush()
            Next

            st.Write(lastBlockBytes, 0, lastBlock)
        End Using
    End Sub

    ''' <summary>
    '''  Almost the same procedure as bytePass, only difference is that there are three different
    '''  bytes passed in as parameters.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub gutmannBytePass(fileInf As FileInfo, byte2write1 As Byte, byte2write2 As Byte, byte2write3 As Byte)
        MyClass.fileLength = fileInf.Length
        MyClass.lastBlock = CInt(fileLength Mod FILE_BLOCK)
        MyClass.lastBlockBytes = New Byte(lastBlock - 1) {}
        MyClass.modWrite = CInt(fileLength / FILE_BLOCK)

        Dim selectByte As Integer = 1

        For i As Integer = 0 To FILE_BLOCK - 1
            Select Case selectByte
                Case 1
                    bytes(i) = byte2write1
                    selectByte = 2
                    Exit Select
                Case 2
                    bytes(i) = byte2write2
                    selectByte = 3
                    Exit Select
                Case 3
                    bytes(i) = byte2write3
                    selectByte = 1
                    Exit Select
            End Select
        Next

        selectByte = 1

        For i As Integer = 0 To lastBlock - 1
            Select Case selectByte
                Case 1
                    lastBlockBytes(i) = byte2write1
                    selectByte = 2
                    Exit Select
                Case 2
                    lastBlockBytes(i) = byte2write2
                    selectByte = 3
                    Exit Select
                Case 3
                    lastBlockBytes(i) = byte2write3
                    selectByte = 1
                    Exit Select
            End Select
        Next

        Using st As Stream = File.OpenWrite(fileInf.FullName)
            For i As Long = 0 To modWrite - 1
                st.Write(bytes, 0, FILE_BLOCK)
                st.Flush()
            Next

            st.Write(lastBlockBytes, 0, lastBlock)
        End Using
    End Sub

#Region "IDisposable Implementation"
    ' Track whether Dispose has been called. 
    Private disposed As Boolean = False

    ' Implement IDisposable. 
    ' Do not make this method virtual. 
    ' A derived class should not be able to override this method. 
    Public Overloads Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)
        ' This object will be cleaned up by the Dispose method. 
        ' Therefore, you should call GC.SupressFinalize to 
        ' take this object off the finalization queue  
        ' and prevent finalization code for this object 
        ' from executing a second time.
        GC.SuppressFinalize(Me)
    End Sub

    ' Dispose(bool disposing) executes in two distinct scenarios. 
    ' If disposing equals true, the method has been called directly 
    ' or indirectly by a user's code. Managed and unmanaged resources 
    ' can be disposed. 
    ' If disposing equals false, the method has been called by the  
    ' runtime from inside the finalizer and you should not reference  
    ' other objects. Only unmanaged resources can be disposed. 
    Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean)
        ' Check to see if Dispose has already been called. 
        If Not Me.disposed Then
            ' If disposing equals true, dispose all managed  
            ' and unmanaged resources. 
            If disposing Then
                ' Dispose managed resources.
                filePath = Nothing
                randomNumber = Nothing
                lastBlock = Nothing
                lastBlockBytes = Nothing
                modWrite = Nothing
                bytes = Nothing
            End If

            ' Call the appropriate methods to clean up  
            ' unmanaged resources here. 
            ' If disposing is false,  
            ' only the following code is executed.

            ' Note disposing has been done.
            disposed = True
        End If
    End Sub

    ' This finalizer will run only if the Dispose method  
    ' does not get called. 
    ' It gives your base class the opportunity to finalize. 
    ' Do not provide finalize methods in types derived from this class. 
    Protected Overrides Sub Finalize()
        ' Do not re-create Dispose clean-up code here. 
        ' Calling Dispose(false) is optimal in terms of 
        ' readability and maintainability.
        Dispose(False)
        MyBase.Finalize()
    End Sub
#End Region
End Class
